#ifndef MESHLAB_ISO_STATISTICS
#define MESHLAB_ISO_STATISTICS

#include <iso_parametrization.h>

template <class ScalarType>
ScalarType geomAverage(const ScalarType &val0,
                           const ScalarType &val1,
                           const ScalarType &exp0,
                           const ScalarType &exp1)
{
    ScalarType v0=pow(val0,exp0);
    ScalarType v1=pow(val1,exp1);
    ScalarType ret=pow((ScalarType)(v0*v1),(ScalarType)(1.0/(exp0+exp1)));
    return (ret);
}

//template <class MeshType>
class Statistic
{
    IsoParametrization *Isoparam;
    typedef IsoParametrization::PScalarType PScalarType;
    typedef IsoParametrization::CoordType CoordType;
    PScalarType Area3d;
    PScalarType AbstractArea;

public:

    void AreaDistorsion(ParamFace *f,PScalarType &distorsion,PScalarType &area_3d)
    {
        const float epsilon=0.000001f;
        const float maxRatio=10.f;
        int indexDomain;
        vcg::Point2f UV0,UV1,UV2;
        /*int domainType=*/Isoparam->InterpolationSpace(f,UV0,UV1,UV2,indexDomain);


        area_3d=((f->P(1)-f->P(0))^(f->P(2)-f->P(0))).Norm()/(PScalarType)2.0;
        PScalarType area_2d=((UV1-UV0)^(UV2-UV0))/(PScalarType)2.0;
        area_3d/=Area3d;
        area_2d/=AbstractArea;

        if (fabs(area_2d)<epsilon)area_2d=epsilon;
        if (fabs(area_3d)<epsilon)area_3d=epsilon;

        PScalarType r0=area_3d/area_2d;
        PScalarType r1=area_2d/area_3d;
        if (r0>maxRatio)r0=maxRatio;
        if (r1>maxRatio)r1=maxRatio;

        distorsion=((r0+r1)/(PScalarType)2.0)-(PScalarType)1.0;
    }

    void AngleDistorsion(ParamFace *f,PScalarType &distortion)
    {
        const float epsilon=0.000001f;
        PScalarType area_3d=((f->P(1)-f->P(0))^(f->P(2)-f->P(0))).Norm();
        int indexDomain;
        vcg::Point2f UV0,UV1,UV2;
        /*int domainType=*/Isoparam->InterpolationSpace(f,UV0,UV1,UV2,indexDomain);
        PScalarType area_2d=fabs((UV1-UV0)^(UV2-UV0));
        PScalarType a2=(f->P(1)-f->P(0)).SquaredNorm();
        PScalarType b2=(f->P(2)-f->P(1)).SquaredNorm();
        PScalarType c2=(f->P(0)-f->P(2)).SquaredNorm();
        PScalarType cot_a=((UV2-UV1)*(UV0-UV2));
        PScalarType cot_b=((UV0-UV2)*(UV1-UV0));
        PScalarType cot_c=((UV1-UV0)*(UV2-UV1));

        PScalarType num;
        if ((fabs(area_2d)<epsilon)||(fabs(area_3d)<epsilon))
        {
            distortion=0;
            num=0;
        }
        else
        {
            num=(cot_a*a2+cot_b*b2+cot_c*c2)/area_2d;
            distortion=fabs(num/(Area3d*(PScalarType)4.0));
            assert(distortion<1);
        }
    }

    PScalarType AreaDistorsion()
    {
        PScalarType sum=0;
        ParamMesh *param_mesh=Isoparam->ParaMesh();
        for (unsigned int i=0;i<param_mesh->face.size();i++)
        {
            float area3d=0;
            PScalarType distorsion=0;
            AreaDistorsion(&param_mesh->face[i],distorsion,area3d);
            sum+=distorsion*area3d;
        }
        return(fabs(sum));
    }

    PScalarType AngleDistorsion()
    {
        PScalarType sum=0;
        ParamMesh *param_mesh=Isoparam->ParaMesh();
        for (unsigned int i=0;i<param_mesh->face.size();i++)
        {
            PScalarType distorsion=0;
            AngleDistorsion(&param_mesh->face[i],distorsion);
            sum+=distorsion;
        }
        return ((sum)-(PScalarType)1.0);
    }

    PScalarType AggregateDistorsion()
    {
        PScalarType d0=AreaDistorsion();
        PScalarType d1=AngleDistorsion();
        PScalarType ret=geomAverage<PScalarType>(d0+(PScalarType)1.0,d1+(PScalarType)1.0,3,1)-(PScalarType)1;
        return ret;
    }

    void PrintAttributes()
    {
        printf("\n STATISTICS \n"),
        printf("AREA	   distorsion:%lf ;\n",AreaDistorsion());
        printf("ANGLE	   distorsion:%lf ;\n",AngleDistorsion());
        printf("AGGREGATE  distorsion:%lf ;\n",AggregateDistorsion());
        printf("L2 STRETCH efficiency:%lf ;\n",L2Error());
    }


    PScalarType  L2Error()
    {
        ///equilateral triagle
        vcg::Point2f p0(-0.5,0.0);
        vcg::Point2f p1(0.5,0.0);
        vcg::Point2f p2(0,0.866025f);
        ParamMesh::FaceIterator Fi;

        float sum=0;
        float A3dtot=0;
        float A2dtot=0;
        ParamMesh *param_mesh=Isoparam->ParaMesh();
        for (Fi=param_mesh->face.begin();Fi!=param_mesh->face.end();Fi++)
        {
            if (!(*Fi).IsD())
            {
                ParamFace *f=&(*Fi);
                CoordType q1=(*Fi).V(0)->P();
                CoordType q2=(*Fi).V(1)->P();
                CoordType q3=(*Fi).V(2)->P();
                ///map to equilateral triangle
                /*vcg::Point2f UV1=p0*(*Fi).V(0)->Bary.X()+p1*(*Fi).V(0)->Bary.Y()+p2*(*Fi).V(0)->Bary.Z();
                vcg::Point2f UV2=p0*(*Fi).V(1)->Bary.X()+p1*(*Fi).V(1)->Bary.Y()+p2*(*Fi).V(1)->Bary.Z();
                vcg::Point2f UV3=p0*(*Fi).V(2)->Bary.X()+p1*(*Fi).V(2)->Bary.Y()+p2*(*Fi).V(2)->Bary.Z();*/
                vcg::Point2f UV1,UV2,UV3;
                int indexDomain;
                /*int domainType=*/Isoparam->InterpolationSpace(f,UV1,UV2,UV3,indexDomain);
                PScalarType s1=UV1.X();
                PScalarType t1=UV1.Y();
                PScalarType s2=UV2.X();
                PScalarType t2=UV2.Y();
                PScalarType s3=UV3.X();
                PScalarType t3=UV3.Y();
                PScalarType A=fabs(((s2-s1)*(t3-t1)-(s3-s1)*(t2-t1))/(PScalarType)2.0);
                if (A<(PScalarType)0.00001)
                    A=(PScalarType)0.00001;
                PScalarType A3d=((q2 - q1) ^ (q3 - q1)).Norm()/(PScalarType)2.0;
                A3dtot+=A3d;
                A2dtot+=A;
                CoordType Ss=(q1*(t2-t3)+q2*(t3-t1)+q3*(t1-t2))/((PScalarType)2.0*A);
                CoordType St=(q1*(s3-s2)+q2*(s1-s3)+q3*(s2-s1))/((PScalarType)2.0*A);
                PScalarType a=Ss*Ss;
                /*PScalarType b=Ss*St;*/
                PScalarType c=St*St;
                PScalarType L2=sqrt((a+c)/(PScalarType)2.0);
                sum+=L2*L2*A3d;
            }
    }
    sum=sqrt(sum/A3dtot)*sqrt(A2dtot/A3dtot);
    return sum;
    }

    void Init(IsoParametrization *_Isoparam)
    {
        float fix_num=sqrt((PScalarType)3.0)/(PScalarType)4.0;

        Isoparam=_Isoparam;
        ParamMesh *param_mesh=Isoparam->ParaMesh();
        AbstractMesh *abstract_mesh=Isoparam->AbsMesh();

        Area3d=vcg::tri::Stat<ParamMesh>::ComputeMeshArea(*param_mesh);
        AbstractArea=(PScalarType)abstract_mesh->fn*fix_num;
    }
};

template <class MeshType>
typename MeshType::ScalarType ApproxAreaDistortion(MeshType &mesh,const int &num_face)
{
    typedef typename MeshType::FaceType FaceType;
    typedef typename MeshType::ScalarType ScalarType;

    const ScalarType maxRatio=10;
    const ScalarType epsilon=(ScalarType)0.000001;

    ScalarType sum=0,div=0;
    ScalarType area_tot=Area<MeshType>(mesh);

    for (unsigned int i=0;i<mesh.face.size();i++)
    {
        FaceType* f=&mesh.face[i];
        if ((f->V(0)->father==f->V(1)->father)&&(f->V(1)->father==f->V(2)->father))
        {
            ScalarType area_3d=((f->P(1)-f->P(0))^(f->P(2)-f->P(0))).Norm()/area_tot;
            ScalarType area_2d=fabs(AreaUV<FaceType>(*f)/((ScalarType)num_face));
            if (fabs(area_2d)<epsilon)area_2d=epsilon;
            if (fabs(area_3d)<epsilon)area_3d=epsilon;
            ScalarType r0=area_3d/area_2d;
            ScalarType r1=area_2d/area_3d;
            if (r0>maxRatio)r0=maxRatio;
            if (r1>maxRatio)r1=maxRatio;
            sum+=area_3d*(r0+r1);
            div+=area_3d;
        }
    }
    return (sum/(div*2))-1.0;
}

template <class MeshType>
typename MeshType::ScalarType ApproxAngleDistortion(MeshType &mesh)
{
    typedef typename MeshType::FaceType FaceType;
    typedef typename MeshType::ScalarType ScalarType;

    //const ScalarType maxRatio=4;
    const ScalarType epsilon=(ScalarType)0.000001;

    ScalarType sum=0,div=0;
    //ScalarType area_tot=Area<MeshType>(mesh);
    vcg::Point2<ScalarType> x_axis(0.5f, (ScalarType)(sqrt(3.0)/2.0));
    vcg::Point2<ScalarType> y_axis(1.0f,0);
    for (unsigned int i=0;i<mesh.face.size();i++)
    {
        FaceType* f=&mesh.face[i];
        if ((f->V(0)->father==f->V(1)->father)&&(f->V(1)->father==f->V(2)->father))
        {
            ScalarType area_3d=((f->P(1)-f->P(0))^(f->P(2)-f->P(0))).Norm();
            vcg::Point2<ScalarType> t0=f->V(0)->Bary.X()*x_axis+f->V(0)->Bary.Y()*y_axis;
            vcg::Point2<ScalarType> t1=f->V(1)->Bary.X()*x_axis+f->V(1)->Bary.Y()*y_axis;
            vcg::Point2<ScalarType> t2=f->V(2)->Bary.X()*x_axis+f->V(2)->Bary.Y()*y_axis;
            ScalarType area_2d=fabs((t1-t0)^(t2-t0));


            ScalarType a2=(f->P(1)-f->P(0)).SquaredNorm();
            ScalarType b2=(f->P(2)-f->P(1)).SquaredNorm();
            ScalarType c2=(f->P(0)-f->P(2)).SquaredNorm();
            ScalarType cot_a=((t2-t1)*(t0-t2));
            ScalarType cot_b=((t0-t2)*(t1-t0));
            ScalarType cot_c=((t1-t0)*(t2-t1));

            ScalarType num;
            if ((fabs(area_2d)<epsilon)||(fabs(area_3d)<epsilon))
                num=0;
            else
                 num=(cot_a*a2+cot_b*b2+cot_c*c2)/area_2d;

            sum+=num;
            //assert(num>=2.0*area_3d);
            div+=area_3d;
        }
    }
    return (ScalarType)(fabs(sum)/(div*2)-1.0);
}

template <class MeshType>
typename MeshType::ScalarType ApproxL2Error(MeshType &mesh)
    {
        typedef typename MeshType::ScalarType ScalarType;
        typedef typename MeshType::CoordType CoordType;
        ///equilateral triagle
        vcg::Point2f p0(-0.5,0.0);
        vcg::Point2f p1(0.5,0.0);
        vcg::Point2f p2(0,0.866025f);
                typename MeshType::FaceIterator Fi;

        ScalarType sum=0;
        ScalarType A3dtot=0;
        ScalarType A2dtot=0;
        for (Fi=mesh.face.begin();Fi!=mesh.face.end();Fi++)
        {
            if (!(*Fi).IsD())
            {
                if (((*Fi).V(0)->father==(*Fi).V(1)->father)&&
                    ((*Fi).V(1)->father==(*Fi).V(2)->father))
                {
                    CoordType q1=(*Fi).V(0)->RPos;
                    CoordType q2=(*Fi).V(1)->RPos;
                    CoordType q3=(*Fi).V(2)->RPos;
                    ///map to equilateral triangle
                    vcg::Point2f UV1=p0*(*Fi).V(0)->Bary.X()+p1*(*Fi).V(0)->Bary.Y()+p2*(*Fi).V(0)->Bary.Z();
                    vcg::Point2f UV2=p0*(*Fi).V(1)->Bary.X()+p1*(*Fi).V(1)->Bary.Y()+p2*(*Fi).V(1)->Bary.Z();
                    vcg::Point2f UV3=p0*(*Fi).V(2)->Bary.X()+p1*(*Fi).V(2)->Bary.Y()+p2*(*Fi).V(2)->Bary.Z();
                    ScalarType s1=UV1.X();
                    ScalarType t1=UV1.Y();
                    ScalarType s2=UV2.X();
                    ScalarType t2=UV2.Y();
                    ScalarType s3=UV3.X();
                    ScalarType t3=UV3.Y();
                    ScalarType A=fabs(((s2-s1)*(t3-t1)-(s3-s1)*(t2-t1))/2.0);
                    if (A<0.00001)
                        A=(ScalarType)0.00001;
                    ScalarType A3d=((q2 - q1) ^ (q3 - q1)).Norm()/2.0;
                    A3dtot+=A3d;
                    A2dtot+=A;
                    CoordType Ss=(q1*(t2-t3)+q2*(t3-t1)+q3*(t1-t2))/(2.0*A);
                    CoordType St=(q1*(s3-s2)+q2*(s1-s3)+q3*(s2-s1))/(2.0*A);
                    ScalarType a=Ss*Ss;
                    /*ScalarType b=Ss*St;*/
                    ScalarType c=St*St;
                    ScalarType L2=sqrt((a+c)/2.0);
                    sum+=L2*L2*A3d;
                }
            }
        }
        sum=sqrt(sum/A3dtot)*sqrt(A2dtot/A3dtot);
        return sum;
    }

#endif // MESHLAB_ISO_STATISTICS
